using System;
using System.Collections.Generic;
using System.Reflection;
using System.Diagnostics;
using System.Text;

namespace Dida
{
	public class TraitMethodResolver : ICloneable
	{
		// TODO: Make it depend less on names.

		Dictionary<string, TraitInfo> traits = new Dictionary<string, TraitInfo>();
		List<Implementation> implementations = new List<Implementation>();
		Dictionary<MethodFootprint, List<Declaration>>
			declarations = new Dictionary<MethodFootprint, List<Declaration>>();
		List<Connection> connections = new List<Connection>();
		
		bool dirty = false;
		bool hasErrors = false;
		ErrorList errors = new ErrorList();

		public void AddTrait(string name,Type traitType,object userData)
		{
			TraitInfo traitInfo = new TraitInfo(name,traitType,userData);
			traits.Add(name,traitInfo);

			ReadImplementations(traitInfo);
			ReadDeclarations(traitInfo);

			dirty = true;
		}

		public void RemoveTrait(string name)
		{
			traits.Remove(name);

			for(int i = implementations.Count - 1;i >= 0;i--)
				if (implementations[i].trait.name == name)
					implementations.RemoveAt(i);

			List<MethodFootprint> toDelete = new List<MethodFootprint>();
			foreach (KeyValuePair<MethodFootprint, List<Declaration>> pair in declarations)
			{
				List<Declaration> decls = pair.Value;
				for (int i = decls.Count - 1; i >= 0; i--)
				{
					if (decls[i].DeclaringTrait.name == name)
					{
						decls.RemoveAt(i);
					}
				}

				if (decls.Count == 0)
					toDelete.Add(pair.Key);
			}

			foreach (MethodFootprint deleteThis in toDelete)
				declarations.Remove(deleteThis);

			dirty = true;
		}

		public void RenameTrait(string oldName, string newName)
		{
			TraitInfo backup = traits[oldName];
			traits.Remove(oldName);
			traits.Add(newName, backup);

			backup.name = newName;
		}

		/// <summary>
		/// Returns all methods that implement the given method.
		/// </summary>
		/// <param name="method">The method for which to find the implementations</param>
		/// <returns>A list of all the implementations for the given method</returns>
		/// <remarks>
		/// The returned list contains a list of <see cref = "Implementation"/>'s,
		/// is invokend.
		/// </remarks>
		public IList<Implementation> GetMethodImplementations(MethodInfo method)
		{
			MethodFootprint footprint = new MethodFootprint(method);
			List<Implementation> ret = new List<Implementation>();
			foreach (Implementation impl in implementations)
			{
				MethodFootprint curFootprint = new MethodFootprint(impl.methodInfo);
				if (curFootprint == footprint)
					ret.Add(impl);
			}
			return ret;
		}

		public IList<Connection> Connections
		{
			get 
			{ 
				if(dirty)
					Recalc();
#if DEBUG
				return new ImmutableList<Connection>(connections);
#else
				return connections;
#endif
			}
		}

		public bool HasErrors
		{
			get
			{
				if (dirty)
					Recalc();
				return hasErrors;
			}
		}

		public ErrorList Errors
		{
			get
			{
				if (dirty)
					Recalc();
				return errors;
			}
		}

		private void Recalc()
		{
			errors = new ErrorList();
			dirty = false;

			ClearResolveInfo();

			hasErrors = false;

			/*bool collisionError = !CheckCollisions(errors);
			bool inheritenceCollisionError = !CheckInheritenceCollisions(errors);
			if (collisionError || inheritenceCollisionError)
				return;*/

			if (!ResolveMethods(errors))
				hasErrors = true;

			if (!Validate(errors))
				hasErrors = true;
		}

		private void ClearResolveInfo()
		{
			foreach (List<Declaration> decls in declarations.Values)
				foreach (Declaration decl in decls)
					decl.ClearImplementations();
		}

		private bool CheckCollisions(ErrorList errors)
		{
			bool success = true;
			foreach (TraitInfo a in traits.Values)
			{
				foreach(TraitInfo b in traits.Values)
				{
					if (a.type == b.type)
					{
						errors.Add(new TraitDuplicatedError(a.type));
						success = false;
					}
				}
			}			
			return success;
		}

		private bool CheckInheritenceCollisions(ErrorList errors)
		{
			Dictionary<Type, List<Type>> inheritenceGroups = new Dictionary<Type, List<Type>>();

			bool success = true;
			foreach(TraitInfo trait in traits.Values)
			{
				Type curTraitType = trait.type;

				// find a group that this type can belong to
				bool groupFound = false;
				foreach (KeyValuePair<Type, List<Type>> inheritingTrait in inheritenceGroups)
				{
					if (curTraitType.IsSubclassOf(inheritingTrait.Key))
					{
						inheritingTrait.Value.Add(curTraitType);
						groupFound = true;
						break;
					}
				}

				if (!groupFound)
				{
					// find existing groups, who's major type 
					// (highest in the inheritence tree) type is
					// a subclass of the current type. If we find any,
					// we remove them from the inheritingTraits list, 
					// and add them to a new entry, which has the current type
					// as it's major type.
					List<Type> collidingTraits = new List<Type>();
					collidingTraits.Add(curTraitType);

					List<Type> toRemove = new List<Type>();
					foreach (KeyValuePair<Type, List<Type>> inheritingTrait in inheritenceGroups)
					{
						if (inheritingTrait.Key.IsSubclassOf(curTraitType))
						{
							collidingTraits.AddRange(inheritingTrait.Value);

							toRemove.Add(inheritingTrait.Key);
						}
					}

					inheritenceGroups[curTraitType] = collidingTraits;

					foreach (Type t in toRemove)
						inheritenceGroups.Remove(t);
				}
			}

			// if there are any traits that also have a base type in 
			// this traitclass, we "throw" an error
			foreach (KeyValuePair<Type, List<Type>> group in inheritenceGroups)
			{
				if (group.Value.Count > 1)
				{
					// we found an error
					errors.Add(new TraitsInheritError(group.Key, group.Value.ToArray()));
					success = false;
				}
			}

			return success;
		}

		private bool ResolveMethods(ErrorList errors)
		{
			bool success = true;

			// resolve the implementations
			foreach (Implementation impl in implementations)
			{
				List<Declaration> decls;
				if (declarations.TryGetValue(new MethodFootprint(impl.methodInfo), out decls))
				{
					foreach (Declaration decl in decls)
					{
						decl.AddImplementation(impl);

						// add a new connection to the connections list
						connections.Add(new Connection(decl.DeclaringTrait,decl.MethodInfo,
							impl.trait,impl.methodInfo));
					}
				}
				else if (impl.needsDeclaration)
				{
					errors.Add(new UnresolvedImplementationError(
						impl.methodInfo,impl.trait.name));
					success = false;
				}
			}

			return success;
		}

		private bool Validate(ErrorList errorList)
		{
			bool success = true;

			foreach (TraitInfo trait in traits.Values)
			{
				// there must be constructor with zero parameters, so the
				// GetConstructor method shouldn't return null, or if it does,
				// there shouldn't be any other constructors.
				if (trait.type.GetConstructor(new Type[0]) == null &&
					trait.type.GetConstructors().Length != 0)
				{
					errorList.Add(new ConstructorParamsError(trait.type));
					success = false;
				}
			}

			foreach (List<Declaration> decls in declarations.Values)
				foreach (Declaration decl in decls)
					if (!decl.Validate(errorList))
						success = false;

			return success;
		}

		private void ReadImplementations(TraitInfo traitInfo)
		{
			foreach (MethodInfo method in traitInfo.type.GetMethods())
			{
				object[] attribs = method.GetCustomAttributes(
					typeof(TraitImplementMethodAttribute), true);
				if (attribs.Length == 1)
				{
					// if the method has a TraitImplement attribute,
					// we will add it to the implementedMethods list
					implementations.Add(new Implementation(traitInfo, method));
				}
				else
				{
					Debug.Assert(attribs.Length == 0, "You shouldn't specify the " +
						"TraitImplementMethod attribute more than once on a single method");
				}
			}
		}

		private void ReadDeclarations(TraitInfo trait)
		{
			foreach (MethodInfo method in trait.type.GetMethods())
			{
				object[] attribs = method.GetCustomAttributes(
					typeof(TraitDeclareMethodAttribute), true);
				if (attribs.Length == 1)
				{
					Debug.Assert(method.IsAbstract || method.IsVirtual,
						"A trait method declaration must be either abstract or virtual.");

					Declaration decl = new Declaration(trait, method);
					MethodFootprint footprint = new MethodFootprint(method);

					List<Declaration> declsList;
					if (!declarations.TryGetValue(footprint, out declsList))
					{
						declsList = new List<Declaration>();
						declarations.Add(footprint, declsList);
					}
					declsList.Add(decl);
				}
				else
				{
					Debug.Assert(attribs.Length == 0,
						"You shouldn't specify the TraitDeclareMethod " +
						"attribute more than once on a single event");
				}
			}
		}

		internal class TraitInfo
		{
			public string name;
			public Type type;
			public object userData;

			public TraitInfo(string name, Type type, object userData)
			{
				this.type = type;
				this.name = name;
				this.userData = userData;
			}
		}

		private class Declaration
		{
			TraitInfo trait;
			MethodInfo method;
			TraitDeclareMethodAttribute attribute;
			TraitMethodRequirement requirement;

			List<Implementation> implementations = new List<Implementation>();

			public Declaration(TraitInfo trait, MethodInfo method)
			{
				this.trait = trait;
				this.method = method;

				object[] attribs = method.GetCustomAttributes(
					typeof(TraitDeclareMethodAttribute), true);
				Debug.Assert(attribs.Length == 1);

				attribute = (TraitDeclareMethodAttribute)attribs[0];

				if (method.IsAbstract)
					requirement = TraitMethodRequirement.MustImplement;
				else
				{
					Debug.Assert(method.IsVirtual);
					requirement = TraitMethodRequirement.Optional;
				}
			}

			public void ClearImplementations()
			{
				implementations.Clear();
			}

			public void AddImplementation(Implementation impl)
			{
				implementations.Add(impl);
			}

			public bool Validate(ErrorList errorList)
			{
				if (attribute.Acceptance == TraitMethodAcceptance.SingleImplementation
					&& implementations.Count > 1)
				{
					// the trait wants a single implementation, but multiple 
					// are provided, so we throw an error
					errorList.Add(new MultipleImplementationsError(method));
					return false;
				}

				if (requirement == TraitMethodRequirement.MustImplement &&
					implementations.Count == 0)
				{
					// an implementation is required, but none is provided, 
					// so we throw an error.
					errorList.Add(new NoImplementationError(method));
					return false;
				}

				return true;
			}

			public string Name
			{
				get { return method.Name; }
			}

			public TraitInfo DeclaringTrait
			{
				get { return trait; }
			}

			public MethodInfo MethodInfo
			{
				get { return method; }
			}

			public List<Implementation> Implementations
			{
				get { return implementations; }
			}

			public bool HasImplementations
			{
				get { return implementations.Count != 0; }
			}
		}

		public class Implementation
		{
			internal TraitInfo trait;
			internal MethodInfo methodInfo;
			internal bool needsDeclaration;

			internal Implementation(TraitInfo trait, MethodInfo methodInfo)
			{
				this.trait = trait;
				this.methodInfo = methodInfo;

				object[] attrs = methodInfo.GetCustomAttributes(
					typeof(TraitImplementMethodAttribute), true);
				Debug.Assert(attrs.Length == 1);
				TraitImplementMethodAttribute attr = (TraitImplementMethodAttribute)attrs[0];
				needsDeclaration = attr.NeedsDeclaration;
			}

			/// <summary>
			/// Gets the user data associated with the trait that holds this implementation.
			/// </summary>
			/// <remarks>
			/// The user data is the object that was passed as the userData parameter
			/// of the <see cref = "AddTrait"/> method.
			/// </remarks>
			public object ImplementingTraitUserData
			{
				get { return trait.userData; }
			}

			public MethodInfo MethodInfo
			{
				get { return methodInfo; }
			}
		}

		public class Connection
		{
			TraitInfo declaringTrait;
			MethodInfo declaringMethod;
			TraitInfo implementingTrait;
			MethodInfo implementingMethod;

			internal Connection(TraitInfo declaringTrait, MethodInfo declaringMethod,
				TraitInfo implementingTrait, MethodInfo implementingMethod)
			{
				this.declaringTrait = declaringTrait;
				this.declaringMethod = declaringMethod;
				this.implementingTrait = implementingTrait;
				this.implementingMethod = implementingMethod;
			}

			public string DeclaringTraitName
			{
				get { return declaringTrait.name; }
			}

			public string ImplementingTraitName
			{
				get { return implementingTrait.name; }
			}
		}

		#region ICloneable Members

		public object Clone()
		{
			TraitMethodResolver ret = new TraitMethodResolver();
			foreach (TraitInfo traitInfo in traits.Values)
				ret.AddTrait(traitInfo.name, traitInfo.type, traitInfo.userData);
			return ret;
		}

		#endregion
	}

	public class ErrorList
	{
		List<Error> errors = new List<Error>();

		public void Add(Error err)
		{
			errors.Add(err);
		}

		public string GetConcattedMessages(int indentLevel)
		{
			StringBuilder ret = new StringBuilder();
			foreach (Error err in errors)
			{
				string tabs = new string('\t', indentLevel);
				ret.AppendLine(tabs + err.Message);
			}
			return ret.ToString();
		}

		/// <summary>
		/// Returns an array of all errors that are of the given type,
		/// or of a derived type.
		/// </summary>
		/// <param name="type">The type of the errors you want.</param>
		/// <returns>An array with the errors that are of the given type.</returns>
		public Error[] GetErrorsByType(Type type)
		{
			List<Error> ret = new List<Error>();
			foreach (Error error in errors)
				if (type.IsInstanceOfType(error))
					ret.Add(error);
			return ret.ToArray();
		}

		public IList<Error> Errors
		{
			get 
			{
#if DEBUG
				return new ImmutableList<Error>(errors);
#else
				return errors;
#endif
			}
		}
	}

	public abstract class Error
	{
		/// <summary>
		/// Should get a human readable message, that describes this
		/// error message.
		/// </summary>
		public abstract string Message
		{
			get;
		}
	}

	#region Errors of the method resolver

	public class UnresolvedImplementationError : Error
	{
		private MethodInfo method;
		private string msg;

		internal UnresolvedImplementationError(MethodInfo method,string traitName)
		{
			this.method = method;

			msg = "Unresolved implementation: \"" + method.Name +
				"\" in trait \"" + traitName + "\"";
		}

		/// <summary>
		/// Gets a human readable message, that describes this error.
		/// </summary>
		public override string Message
		{
			get { return msg; }
		}
	}

	public class MultipleImplementationsError : Error
	{
		private MethodInfo method;
		private string msg;

		internal MultipleImplementationsError(MethodInfo method)
		{
			this.method = method;

			msg = "The method declaration " + method.Name + " in " +
				method.ReflectedType.FullName + " expects only one " +
				"implementation, but multiple are provided.";
		}

		public override string Message
		{
			get { return msg; }
		}
	}

	public class NoImplementationError : Error
	{
		private MethodInfo method;
		private string msg;

		internal NoImplementationError(MethodInfo method)
		{
			this.method = method;

			msg = "The trait method " + method.Name +
				" (declared in " + method.DeclaringType.FullName + ") " +
				" requires an implementation, but none is provided";
		}
		
		public MethodInfo Method
		{
			get { return method; }
		}

		public override string Message
		{
			get { return msg; }
		}
	}

	public class ConstructorParamsError : Error
	{
		private Type type;
		private string msg;

		internal ConstructorParamsError(Type type)
		{
			this.type = type;

			msg = "The trait " + type.FullName + " doesn't have a constructor " +
				" that doesn't take any parameters.";
		}
		
		/// <summary>
		/// Gets the type of the trait that triggered this error. 
		/// </summary>
		public Type TraitType
		{
			get { return type; }
		}

		/// <summary>
		/// Gets a human readable message, that describes this error.
		/// </summary>
		public override string Message
		{
			get { return msg; }
		}
	}

	public class TraitDuplicatedError : Error
	{
		Type traitType;

		internal TraitDuplicatedError(Type traitType)
		{
			this.traitType = traitType;
		}
		
		/// <summary>
		/// Gets a human readable message, that describes this error.
		/// </summary>
		public override string Message
		{
			get
			{
				return "The trait " + traitType.FullName + " is specified more than once";
			}
		}
	}

	public class TraitsInheritError : Error
	{
		private Type highestType;
		private Type[] allTypes;

		internal TraitsInheritError(Type highestType, Type[] allTypes)
		{
			this.highestType = highestType;
			this.allTypes = allTypes;
		}

		/// <summary>
		/// Gets a human readable message, that describes this error.
		/// </summary>
		public override string Message
		{
			get
			{
				string ret;
				ret = "A trait class should not contain a trait that's " +
					"derived from another trait, that's included in " +
					"the trait class aswell.\n";
				ret += "The following traits all directly or indirectly inherit from " + highestType.FullName + ":\n";
				foreach (Type type in allTypes)
				{
					ret += "\t" + type.FullName + "\n";
				}
				return ret;
			}
		}

		/// <summary>
		/// Gets the type of the trait that's the highest in the inheritence
		/// hierarchy
		/// </summary>
		public Type HighestType
		{
			get { return highestType; }
		}

		/// <summary>
		/// Gets an array of the types of all traits that collide at least 
		/// with the <see cref = "HighestType">highest trait</see>, and
		/// possibly with other traits aswell.
		/// </summary>
		public Type[] AllTypes
		{
			get { return allTypes; }
		}
	}

	#endregion 
}